Skip to content

chore: error-handling convention rollout (#1738)#1756

Merged
Aureliolo merged 5 commits into
mainfrom
chore/audit-followup-c-1738
May 4, 2026
Merged

chore: error-handling convention rollout (#1738)#1756
Aureliolo merged 5 commits into
mainfrom
chore/audit-followup-c-1738

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Migrates every plain-Exception class in src/synthorg/ to a DomainError-rooted hierarchy and ships scripts/check_domain_error_hierarchy.py -- an AST gate that prevents new violations. The gate fails closed: any new class with a direct Exception / RuntimeError / LookupError / PermissionError / ValueError / TypeError / KeyError / IndexError / AttributeError / OSError / IOError base must either reach DomainError via another base or carry a per-line # lint-allow: domain-error-hierarchy -- <reason> opt-out.

The gate landed alongside the migration; the frozen baseline is empty, so --no-baseline exit 0 on first commit.

Closes #1738.

Migrations

Cited families (Sub-tasks 2A-2D):

  • backup/errors.py -- BackupError + 8 subclasses; handle_backup_error removed in favour of handle_domain_error reading ClassVars.
  • config/errors.py -- ConfigError chain (incl. ConfigValidationError -> 422).
  • core/registry/errors.py -- StrategyFactoryError chain.
  • templates/errors.py -- auto-rooted via ConfigError.
  • hr/errors.py (HRError + ~22 subclasses), hr/scaling/errors.py, security/trust/errors.py.
  • settings/errors.py -- SettingNotFoundError -> 404, SettingValidationError -> 422.
  • meta/mcp/errors.py -- ArgumentValidationError (-> ValidationError/422), GuardrailViolationError (-> ForbiddenError/403). The MCP-layer domain_code ClassVar ("invalid_argument" / "guardrail_violated") is preserved alongside the new RFC 9457 metadata.

5 version-controllers -- Budget / Company / EvaluationConfig / Role / Workflow VersionController switch from Response(content=ApiResponse[...](error=...), status_code=...) to raise NotFoundError / ValidationError / VersionConflictError. The ApiResponse[T] envelope shape is preserved on the wire; the centralised handle_domain_error produces it via MRO dispatch.

Sweep (Sub-task 3):

  • core/persistence_errors.py -- PersistenceError(Exception) -> PersistenceError(DomainError). Inner VersionConflictError renamed to PersistenceVersionConflictError to dispel the name collision with core.domain_errors.VersionConflictError. Custom handlers (handle_record_not_found, handle_persistence_integrity_error, handle_duplicate_record, handle_persistence_error) stay registered so persistence-layer 4xx responses keep their fixed public messages instead of leaking record identifiers.
  • ~16 scattered classes across a2a/, api/auth/, api/cursor.py, communication/{event_stream, mcp_errors, meeting/agent_caller}, engine/quality/decomposers/llm.py, engine/strategy/principles.py, engine/workflow/{blueprint_errors, service}.py, meta/{appliers, errors}.py, telemetry/{privacy, reporters}.py.
  • 7 internal-sentinel classes carry per-line allowlist markers (TsaError, MCP handler-local _NotFoundError / _ConflictError, statistical Welch errors, dotted-path validators, internal CAS/transaction-rollback sentinels) where stdlib bases are deliberate per the docstrings.

AST gate

  • scripts/check_domain_error_hierarchy.py (~625 lines): two-pass AST resolver -- pass 1 indexes every class definition under src/synthorg/, pass 2 computes the closure of (module, class) pairs reaching DomainError. Every direct stdlib base outside that closure is a violation unless suppressed.
  • Per-line opt-out: # lint-allow: domain-error-hierarchy -- <non-empty reason> (regex-based to survive ruff-format multi-line class headers).
  • Frozen baseline: scripts/domain_error_hierarchy_baseline.txt (header-only on land; --update-baseline regenerates).
  • Pre-push hook in .pre-commit-config.yaml (stages: [pre-push]).
  • 27 unit tests in tests/unit/scripts/test_check_domain_error_hierarchy.py covering positive cases (DomainError direct subclass, intermediate, deeper MRO chain, allowlist marker, baseline suppress, module-import alias), negative cases (every forbidden stdlib base), baseline format, baseline drift, --no-baseline, --update-baseline.

Tests

Pre-existing unit suite: 26597 passed, 16 skipped. The pytest tests/ -m unit -n 8 baseline is preserved.

New tests:

  • tests/unit/scripts/test_check_domain_error_hierarchy.py -- 27 tests for the gate.
  • tests/unit/api/test_exception_handlers.py::TestMigratedDomainErrorFamilies -- 14 parametrized e2e tests covering ClassVar -> HTTP mapping for every migrated HR / Scaling / Trust / Settings / Config / registry family. Catches status_code / error_code / error_category typos that the AST gate cannot.
  • tests/unit/api/controllers/test_workflow_versions.py::TestRollback::test_rollback_persistence_version_conflict_translates_to_409 -- pins the PersistenceVersionConflictError -> VersionConflictError translation in the workflow rollback controller (the only late-path translation in the repo).
  • tests/unit/api/controllers/test_workflow_versions.py::test_diff_same_version_returns_validation_error -- now asserts the full RFC 9457 envelope (error_code, error_category, retryable), not just the status code.

Documentation

  • CLAUDE.md -- "Errors" bullet now lists the full 11-base forbidden set and references the new gate + opt-out marker.
  • docs/reference/errors.md -- new "Domain-error-hierarchy gate" section.
  • docs/reference/conventions.md Section 6 -- cross-links the new gate.

Pre-PR review

Ran /pre-pr-review with 14 specialized agents. 12 valid findings: 2 CRITICAL + 5 MAJOR + 3 MEDIUM + 2 MINOR/INFO. All implemented in the second commit on this branch.

Reviewer agents with zero findings: conventions-enforcer, logging-audit, persistence-reviewer, issue-resolution-verifier, api-contract-drift, infra-reviewer.

Test plan

  • uv run python -m pytest tests/ -m unit -n 8 -- 26597 passed, 16 skipped.
  • uv run python scripts/check_domain_error_hierarchy.py --no-baseline -- exit 0.
  • uv run mypy src/ tests/ -- 0 issues across 3648 files.
  • uv run ruff check src/ tests/ scripts/ -- clean.
  • uv run ruff format src/ tests/ scripts/ --check -- 3684 files already formatted.
  • uv run pre-commit run --hook-stage pre-push domain-error-hierarchy --all-files -- passes.

Push note

Pre-push isolation regression gate (pytest --count 2 -x -n 8 over the affected suite) reports xdist worker crashes in 8 unrelated test files (auth/postgres_session_store, controllers/{collaboration, department_mutations, meetings, provider_health}, auth/{csrf, lockout_store}, webhook_receipt_cleanup_loop). Manual single-pass green; isolation-gate doubled-run on Python 3.14 + Windows ProactorEventLoop crashes the worker, marking 16 of the new gate tests as collateral damage. Filed #1755 with full details + suggested fixes. Pushed with --no-verify per per-push authorization.

Issue tracker

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e03f187d-ebb5-4c8b-929f-647f76ee7478

📥 Commits

Reviewing files that changed from the base of the PR and between 8714c5e and 3634c39.

📒 Files selected for processing (1)
  • src/synthorg/engine/workflow/execution_lifecycle.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Lighthouse Site
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

The src/synthorg/persistence/ directory is the only place that may import aiosqlite / sqlite3 / psycopg / psycopg_pool or emit raw SQL DDL/DML. Controllers and API endpoints access persistence through domain-scoped service layers; services centralize audit logging; repositories must not log mutations themselves.

No default may privilege a region, currency, or locale. Resolution: user/company → browser/system → neutral fallback. Currency, locale, timezone, date/number formats all flow through @/utils/format + @/utils/locale (frontend) and DEFAULT_CURRENCY from synthorg.budget.currency (backend); no _usd suffixes; metric units only; International / British English UI default. Every cost-bearing Pydantic model carries currency: CurrencyCode; mixing raises MixedCurrencyAggregationError (HTTP 409).

Comments explain WHY only, never origin / review / issue context. Forbidden: reviewer citations, in-code issue back-refs, naked SEC-1 taxonomy in src/, migration framing, round narrative, self-evident restatements.

Use PEP 758 except: except A, B: (no parens) when not binding; as exc requires parens.

Type hints: all public functions; mypy strict.

Docstrings: Google style on public classes / functions (ruff D rules).

Never mutate objects; create new objects via model_copy(update=...) or copy.deepcopy(). Frozen Pydantic for config/identity; MappingProxyType for non-Pydantic registries; deepcopy at system boundaries (tool execution, provider serialization, persistence).

Separate frozen config models from mutable-via-copy runtime models; never mix in one model.

Pydantic v2: ConfigDict(frozen=True, allow_inf_nan=False) everywhere; extra="forbid" on every model that doesn't round-trip through model_dump() (request DTOs always); @computed_field for derived values; NotBlankStr from core.types for identifier / name fields.

Request DTOs must declare extra="forbid" in Pydantic ConfigDict. Enforced by `scripts/check_request_dto_...

Files:

  • src/synthorg/engine/workflow/execution_lifecycle.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations in Python source; Python 3.14 has PEP 649.

Python: 3.14+ (PEP 649 native lazy annotations).

Files:

  • src/synthorg/engine/workflow/execution_lifecycle.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/engine/workflow/execution_lifecycle.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Always read the relevant `docs/design/` page before implementing or planning. Deviations require explicit user approval; update the design page when an approved deviation lands. Never silently diverge.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding. Be critical at every phase; surface improvements as suggestions, not silent changes. Prioritize by dependency order, not priority labels.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Read `docs/guides/persistence-migrations.md`; never hand-edit SQL or `atlas.sum`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Enforce persistence boundary via `scripts/check_persistence_boundary.py`; per-line opt-out: `# lint-allow: persistence-boundary -- <reason>`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Never use `cd` in Bash commands; cwd is already project root. Exception: `bash -c "cd <dir> && <cmd>"` is safe (child process).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Never use Bash to write files: use Write or Edit. Forbidden: `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)"`, `tee`. Read-only piping to stdout is fine.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: When tests fail due to timeout / slowness / xdist contention: NEVER delete, skip, or `xfail`; NEVER `--no-verify`; NEVER edit `tests/baselines/unit_timing.json`. Suite time exceeding `baseline * 1.3` is a source-code regression; fix the source, not the tests.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Coverage: 80% minimum (CI; benchmarks excluded).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Parallelism: `pytest-xdist -n 8 --dist=loadfile` (always). `loadfile` prevents the cumulative resource leak `worksteal` triggers on Python 3.14 + Windows ProactorEventLoop.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: `scripts/run_affected_tests.py` re-runs the affected subset twice via `pytest-repeat --count 2 -x` after the green pass (isolation regression gate). Opt out via `SYNTHORG_SKIP_ISOLATION_GATE=1`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Flaky tests: NEVER skip/xfail/dismiss; fix fundamentally. FakeClock-first when the class accepts `clock=`. For "block until cancelled", use `asyncio.Event().wait()` not `asyncio.sleep(large)`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Commits: `<type>: <description>` (feat/fix/refactor/docs/test/chore/perf/ci); enforced by commitizen.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Signed commits: required on every protected ref. GPG/SSH signed, OR GitHub App-signed via the `synthorg-repo-bot`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Branches: `<type>/<slug>` from main.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Hookify rules (`.claude/hookify.*.md`): `block-pr-create` (must use `/pre-pr-review`), `block-double-push` (5-min throttle when an open PR exists), `enforce-parallel-tests` (`-n 8`), `no-cd-prefix`, `no-local-coverage`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Pre-push hooks: mypy + pytest (affected modules) + golangci-lint + go vet + go test (CLI) + eslint-web + `orphan-fixtures` (opt-in via `SYNTHORG_CHECK_ORPHAN_FIXTURES=1`) + `setting-to-startup-trace` (conditional). Foundational module changes (core, config, observability) or conftest edits trigger full runs.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: GitHub issue queries: `gh issue list` via Bash, NOT MCP `list_issues` (unreliable field data).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Merge strategy: squash. PR body becomes the squash commit message; trailers (`Release-As`, `Closes `#N``) must be in the PR body to land.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: PR issue references: preserve existing `Closes `#NNN``; never remove unless explicitly asked.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: After finishing an issue: branch (`<type>/<slug>`), commit, push. Do NOT auto-create a PR. ALWAYS use `/pre-pr-review` to create PRs (`gh pr create` is hookify-blocked). Trivial / docs-only: `/pre-pr-review quick`. After the PR exists, `/aurelio-review-pr` handles external reviewer feedback.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Fix EVERYTHING valid review agents find, including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes. No deferring, no "out of scope".
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Any PR that establishes or expands a project-wide convention (error hierarchies, persistence boundary, mock-spec, regional defaults, typed boundary, settings-to-startup wiring, secret-log redaction, request-DTO `extra="forbid"`, no-magic-numbers, no-em-dashes, etc.) MUST include the AST/script gate that prevents regression. PRs proposing a convention without enforcement are rejected.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Enforced by `scripts/check_backend_regional_defaults.py` and `scripts/check_forbidden_literals.py`. Per-line opt-out: `# lint-allow: regional-defaults`. See [docs/reference/regional-defaults.md](docs/reference/regional-defaults.md).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Layout: `src/synthorg/` (src layout), `tests/`, `web/` (React 19 dashboard), `cli/` (Go CLI binary).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: Web: see `web/CLAUDE.md`. Async-leak ceiling lives in `.github/ci/web-async-leaks.max`; full-suite check is `npm --prefix web run test -- --coverage --detect-async-leaks`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T22:21:25.851Z
Learning: CLI: see `cli/CLAUDE.md`. Use `go -C cli` (never `cd cli`).
🔇 Additional comments (4)
src/synthorg/engine/workflow/execution_lifecycle.py (4)

16-19: LGTM!

Import migration correctly switches to the persistence-layer exceptions (PersistenceVersionConflictError, RecordNotFoundError) that root into DomainError hierarchy, aligning with the PR's error-convention rollout.


288-313: LGTM!

Well-factored helper functions that centralize the event-to-handler routing and idempotency logic. The consistent status-set checks across _retry_event_for, _dispatch_task_handler, and _expected_terminal_node_status prevent future drift.


316-368: LGTM — past review concern addressed.

The retry function now:

  1. Re-raises MemoryError / RecursionError via PEP 758 syntax (line 353)
  2. Explicitly handles RecordNotFoundError and PersistenceVersionConflictError with warnings
  3. Lets any other unexpected exception propagate (removed catch-all)

Idempotency guards (terminal-status check, already-applied node-status check) prevent bogus re-dispatches after a successful racing writer.


391-402: LGTM!

The updated exception handling correctly:

  • Drops RecordNotFoundError with a warning (retrying a deleted row is futile)
  • Delegates PersistenceVersionConflictError to the single-retry path
  • Lets other exceptions (including fatal system errors) propagate

The inline comment explains the drop rationale.


Walkthrough

The commit migrates many exception classes under src/synthorg/ to DomainError-rooted families and adds ClassVar metadata (default_message, error_category, error_code, status_code). It introduces an AST-based gate scripts/check_domain_error_hierarchy.py with a frozen baseline file and a local pre-commit/pre-push hook, and documents the gate in docs/reference/errors.md and docs/reference/conventions.md. Several controllers now raise domain exceptions (NotFoundError, ValidationError) instead of returning Response objects. VersionConflictError was replaced by PersistenceVersionConflictError and corresponding imports, tests, and persistence repos were updated.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview May 4, 2026 20:06 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a standardized domain error hierarchy and an AST-based pre-commit gate to enforce it. The changes include a new validation script, updated documentation, and the migration of numerous exception classes to inherit from DomainError. Additionally, persistence-layer conflict errors were renamed to avoid naming collisions. Review feedback identified a critical Python 3 syntax error in the gate script, a logic error in how imports are resolved, and an issue with non-recursive file scanning.

capture_output=True,
cwd=project_root,
)
except subprocess.CalledProcessError, FileNotFoundError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This line contains a syntax error in Python 3. The comma-separated syntax for catching multiple exceptions (except E1, E2:) was removed in favor of tuples (except (E1, E2):). Furthermore, in Python 3, the comma syntax is interpreted as except E1 as E2:, meaning this code currently catches CalledProcessError and renames it to FileNotFoundError, while failing to catch actual FileNotFoundError exceptions (which occur if git is not installed).

Suggested change
except subprocess.CalledProcessError, FileNotFoundError:
except (subprocess.CalledProcessError, FileNotFoundError):

Comment thread scripts/check_domain_error_hierarchy.py Outdated
aliases[alias.asname] = (alias.name, None)
else:
head = alias.name.split(".")[0]
aliases.setdefault(head, (alias.name, None))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This logic incorrectly maps the root package name to the full imported module path when no alias is used. In Python, import a.b.c adds a to the local namespace, not a.b.c. This causes the AST resolver to incorrectly compute the module path for base classes in files using non-aliased imports (e.g., it would resolve synthorg.core.domain_errors.DomainError as synthorg.core.domain_errors.core.domain_errors.DomainError).

                    aliases.setdefault(head, (head, None))

rel_root = abs_root.relative_to(project_root).as_posix() or "."
try:
result = subprocess.run(
["git", "ls-files", "-z", "--", f"{rel_root}/*.py"],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current git ls-files command uses a shell-style glob *.py which is not recursive in this context. It will only find Python files in the immediate directory specified by rel_root, skipping all subdirectories (e.g., src/synthorg/core/). To ensure the gate scans the entire tree, you should pass the directory path to git and filter by extension in Python.

            ["git", "ls-files", "-z", "--", rel_root],

for p in sorted(abs_root.rglob("*.py"))
]
out = result.stdout.decode("utf-8", errors="replace")
paths = [p for p in out.split("\0") if p]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since the git ls-files command should be updated to return all tracked files in the directory to ensure recursion, you must filter the results here to only include Python files.

    paths = [p for p in out.split("\0") if p and p.endswith(".py")]

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 4, 2026

Merging this PR will not alter performance

✅ 33 untouched benchmarks
⏩ 21 skipped benchmarks1


Comparing chore/audit-followup-c-1738 (3634c39) with main (0a46e19)2

Open in CodSpeed

Footnotes

  1. 21 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (d093cb9) during the generation of this report, so 0a46e19 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/synthorg/backup/errors.py (1)

59-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

BackupUnrestartableError is outside the BackupError family contract.

Line 3 says all backup-related errors inherit BackupError, but Line 59 inherits ConflictError directly. That means except BackupError will not catch this class.

💡 Proposed fix
-from synthorg.core.domain_errors import ConflictError, DomainError
+from synthorg.core.domain_errors import DomainError
 from synthorg.core.error_taxonomy import ErrorCategory, ErrorCode
@@
-class BackupUnrestartableError(ConflictError):
+class BackupUnrestartableError(BackupError):
@@
     default_message: ClassVar[str] = (
         "Backup scheduler is unrestartable after a timed-out stop"
     )
+    error_category: ClassVar[ErrorCategory] = ErrorCategory.CONFLICT
+    error_code: ClassVar[ErrorCode] = ErrorCode.RESOURCE_CONFLICT
+    status_code: ClassVar[int] = 409
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/backup/errors.py` around lines 59 - 69, The
BackupUnrestartableError class currently subclasses ConflictError directly which
breaks the BackupError family contract; change its base class to BackupError
(ensuring BackupError itself still maps to ConflictError as intended) so that
"except BackupError" will catch BackupUnrestartableError; keep the existing
default_message and docstring intact and update the class definition for
BackupUnrestartableError to inherit from BackupError instead of ConflictError.
src/synthorg/meta/appliers/code_applier.py (1)

432-434: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update _write_changes Raises docstring to match actual exception type.

Line 433 still documents RuntimeError, but this path now wraps failures in PartialWriteError. The stale contract can mislead callers and tests.

Proposed docstring fix
         Raises:
-            RuntimeError: If a file write or delete fails.
+            PartialWriteError: If applying a change fails after one or more
+                changes were written; carries the applied subset for rollback.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/meta/appliers/code_applier.py` around lines 432 - 434, The
_write_changes docstring incorrectly documents that it raises RuntimeError;
update the Raises section to list PartialWriteError (the actual exception type
now used when file write/delete fails) and adjust any explanatory text to
reference PartialWriteError and its semantics; locate and modify the
_write_changes method's docstring to replace "RuntimeError" with
"PartialWriteError" and ensure the description matches the wrapped failure
behavior.
src/synthorg/hr/errors.py (1)

17-29: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't default the whole HR family to INTERNAL_ERROR/500.

HRError now makes every subtype without an override serialize as a generic 500. In this file that newly affects business-state exceptions like HiringApprovalRequiredError, HiringRejectedError, and PromotionApprovalRequiredError, which are not server faults. Give those subtypes explicit 4xx metadata or move them under a more specific semantic base before handle_domain_error starts reading these ClassVars.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/hr/errors.py` around lines 17 - 29, HRError currently sets
error_category=ErrorCategory.INTERNAL and status_code=500 so any subclass that
doesn't override those ClassVars will serialize as a 500; update the
business-state exceptions (e.g., HiringApprovalRequiredError,
HiringRejectedError, PromotionApprovalRequiredError) to either inherit from a
new semantic base (e.g., HRClientError) that sets
error_category=ErrorCategory.CLIENT, error_code=ErrorCode.INVALID_REQUEST (or
appropriate 4xx code) and status_code=400/403 as applicable, or explicitly set
those ClassVars on each subclass, and ensure handle_domain_error will read the
new ClassVars for correct 4xx responses.
src/synthorg/persistence/postgres/workflow_execution_repo.py (1)

228-239: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Differentiate delete races from true version conflicts.

rowcount == 0 here means either “version mismatch” or “row no longer exists”. Raising PersistenceVersionConflictError for both collapses a delete race into a 409 conflict and loses the not-found path. Please probe for the current row/version before choosing the exception, and keep the SQLite backend aligned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/persistence/postgres/workflow_execution_repo.py` around lines
228 - 239, The save/update path that currently treats cur.rowcount == 0 as a
blanket PersistenceVersionConflictError should distinguish a deleted row from a
real version mismatch: after detecting rowcount == 0 in the save function (the
method around where PERSISTENCE_WORKFLOW_EXEC_SAVE_FAILED and
PersistenceVersionConflictError are used), run a SELECT version FROM
workflow_executions WHERE id = <execution.id> to see if the row exists; if no
row is returned raise the repository's not-found error (aligning with the SQLite
backend behavior), otherwise raise PersistenceVersionConflictError and include
the current version value in the error message/log so callers can see the true
version mismatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.pre-commit-config.yaml:
- Around line 277-287: The pre-commit hook "domain-error-hierarchy" is only
registered for the pre-push stage; update the .pre-commit-config.yaml entry for
id "domain-error-hierarchy" to include the pre-commit stage as well (e.g.,
change the stages value from [pre-push] to include pre-commit such as
[pre-commit, pre-push]) so the gate runs at commit time as required by the
rollout objective.

In `@scripts/check_domain_error_hierarchy.py`:
- Around line 377-388: The rooted short-circuit currently skips entries as soon
as they reach DomainError, allowing mixed inheritance like class
Foo(DomainError, ValueError) to bypass checks; move or add the call to
_has_forbidden_direct_base(entry) before the rooted membership check (the loop
over all_entries that uses variables entry, rooted, and key) so that you first
test _has_forbidden_direct_base(entry) and only then apply the rooted fast-path;
apply the same change in the second scan block (the analogous code around the
482-489 region) so forbidden direct bases are reported unless explicitly
allowlisted.

In `@src/synthorg/api/cursor.py`:
- Around line 27-37: The docstring for InvalidCursorError is out of sync: it
says controllers should translate this to HTTP 400 but the class sets
status_code = 422; update the docstring to reflect the implemented status (HTTP
422 Unprocessable Entity) and any guidance on controller mapping so it matches
the status_code ClassVar on InvalidCursorError and the error_category/error_code
metadata.

In `@src/synthorg/api/services/workflow_rollback_service.py`:
- Around line 9-13: The service docstring in the workflow_rollback_service
module includes controller/HTTP-level translation details (mentions durable
definition save, PersistenceVersionConflictError,
synthorg.core.domain_errors.VersionConflictError, RFC 9457 and "produces a 409")
which should be removed; edit the module- and function-level docstrings to keep
only layer-local contract and behavior (e.g., what exceptions the service itself
raises such as PersistenceVersionConflictError) and delete any references to
controllers, HTTP mapping, or RFC/error-code dispatching so the docstring stays
focused on the service's local responsibilities.

In `@src/synthorg/engine/strategy/principles.py`:
- Around line 294-298: The try/except around ConstitutionalPrinciple(**raw)
currently raises StrategyPackValidationError without logging; before raising,
log the validation failure at WARNING (including index i, the raw payload, and
the exception message) or emit the existing strategy-pack failure event so
malformed config.custom entries surface; add a logger.warning(...) or call to
the module's strategy-pack failure event helper with context (i, raw, exc)
immediately inside the except block, then raise StrategyPackValidationError as
before.

In `@tests/unit/api/controllers/test_setup_locales.py`:
- Around line 268-271: Replace the three bare AsyncMock assignments to
settings_svc.get_entry with AsyncMock that include a concrete spec;
specifically, when you reassign settings_svc.get_entry (the three occurrences
using AsyncMock with side_effect SettingNotFoundError(...)), pass
spec=settings_svc.__class__ (or the concrete SettingsService class) to AsyncMock
so the mock adheres to the real service interface; update all three locations
that currently call AsyncMock(...) to AsyncMock(spec=settings_svc.__class__,
...).

In `@tests/unit/api/controllers/test_setup.py`:
- Around line 1208-1211: Replace the ad-hoc AsyncMock assignments for
settings_svc.get_entry with AsyncMock(..., spec=original) (use the saved
original = settings_svc.get_entry) so each mock declares a spec; update both
places that assign settings_svc.get_entry (the AsyncMock that raises
SettingNotFoundError and the other occurrence around line ~1256) to pass
spec=original to the AsyncMock constructor to comply with the test mocking rule.

In `@tests/unit/api/controllers/test_workflow_versions.py`:
- Around line 347-356: Move the block-local imports in the test
test_rollback_persistence_version_conflict_translates_to_409 into module scope:
hoist imports for WorkflowRollbackService, ErrorCategory, ErrorCode, and
PersistenceVersionConflictError out of the test body to the top of the test
module so the test function body falls under the 50-line limit; keep the exact
imported names and do not change any behavior or references inside the test.

In `@tests/unit/api/test_exception_handlers.py`:
- Around line 1640-1647: Remove the explicit issue back-reference and
migration-framing language from the test docstring and nearby comment blocks
(the docstring containing "End-to-end ClassVar -> HTTP mapping for the `#1738`
migration" and the similar text at the other location), and replace with a
concise, test-focused explanation of what the test validates (e.g., "Verify
ClassVar->HTTP metadata mapping and that typos in
status_code/error_code/error_category are caught for DomainError subclasses").
Update the docstrings/comments that reference "#1738", "migration", or similar
review/issue context so they only describe the test's purpose and expectations;
preserve mentions of the problematic attributes (status_code, error_code,
error_category) and the fact the test catches typos. Ensure changes touch the
module/test docstring and the other comment block around the same test (the
blocks referenced above).

In `@tests/unit/scripts/test_check_domain_error_hierarchy.py`:
- Line 416: The tests currently call monkeypatch.chdir(project_root) which masks
bugs where main() ignores the --repo-root flag; change those calls so the
current working directory is NOT the repo_root under test (e.g., remove
monkeypatch.chdir(project_root) or set CWD to a different temp dir such as
tmp_path or project_root.parent) so the CLI is forced to resolve paths from the
provided --repo-root; update all occurrences of monkeypatch.chdir(project_root)
(the calls at the three locations noted) and ensure the test invokes main() with
the --repo-root argument pointing to project_root so the flag contract is
actually exercised.

---

Outside diff comments:
In `@src/synthorg/backup/errors.py`:
- Around line 59-69: The BackupUnrestartableError class currently subclasses
ConflictError directly which breaks the BackupError family contract; change its
base class to BackupError (ensuring BackupError itself still maps to
ConflictError as intended) so that "except BackupError" will catch
BackupUnrestartableError; keep the existing default_message and docstring intact
and update the class definition for BackupUnrestartableError to inherit from
BackupError instead of ConflictError.

In `@src/synthorg/hr/errors.py`:
- Around line 17-29: HRError currently sets
error_category=ErrorCategory.INTERNAL and status_code=500 so any subclass that
doesn't override those ClassVars will serialize as a 500; update the
business-state exceptions (e.g., HiringApprovalRequiredError,
HiringRejectedError, PromotionApprovalRequiredError) to either inherit from a
new semantic base (e.g., HRClientError) that sets
error_category=ErrorCategory.CLIENT, error_code=ErrorCode.INVALID_REQUEST (or
appropriate 4xx code) and status_code=400/403 as applicable, or explicitly set
those ClassVars on each subclass, and ensure handle_domain_error will read the
new ClassVars for correct 4xx responses.

In `@src/synthorg/meta/appliers/code_applier.py`:
- Around line 432-434: The _write_changes docstring incorrectly documents that
it raises RuntimeError; update the Raises section to list PartialWriteError (the
actual exception type now used when file write/delete fails) and adjust any
explanatory text to reference PartialWriteError and its semantics; locate and
modify the _write_changes method's docstring to replace "RuntimeError" with
"PartialWriteError" and ensure the description matches the wrapped failure
behavior.

In `@src/synthorg/persistence/postgres/workflow_execution_repo.py`:
- Around line 228-239: The save/update path that currently treats cur.rowcount
== 0 as a blanket PersistenceVersionConflictError should distinguish a deleted
row from a real version mismatch: after detecting rowcount == 0 in the save
function (the method around where PERSISTENCE_WORKFLOW_EXEC_SAVE_FAILED and
PersistenceVersionConflictError are used), run a SELECT version FROM
workflow_executions WHERE id = <execution.id> to see if the row exists; if no
row is returned raise the repository's not-found error (aligning with the SQLite
backend behavior), otherwise raise PersistenceVersionConflictError and include
the current version value in the error message/log so callers can see the true
version mismatch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 64d5a9db-3d99-46ad-8988-c0a5577bd899

📥 Commits

Reviewing files that changed from the base of the PR and between 0a46e19 and 3bf01ae.

📒 Files selected for processing (78)
  • .pre-commit-config.yaml
  • CLAUDE.md
  • docs/reference/conventions.md
  • docs/reference/errors.md
  • scripts/check_domain_error_hierarchy.py
  • scripts/domain_error_hierarchy_baseline.txt
  • src/synthorg/a2a/client.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/cursor.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/backup/errors.py
  • src/synthorg/client/factory.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/config/errors.py
  • src/synthorg/core/persistence_errors.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/hr/errors.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/meta/errors.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/settings/errors.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/tools/html_parse_guard.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/unit/meta/test_code_applier.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
💤 Files with no reviewable changes (1)
  • src/synthorg/api/exception_handlers.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Backend
  • GitHub Check: Lighthouse Site
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations: Python 3.14 has PEP 649.

PEP 758 except: except A, B: (no parens) when not binding; as exc requires parens.

Type hints: all public functions; mypy strict.

Docstrings: Google style on public classes / functions (ruff D rules).

Never mutate; create new objects via model_copy(update=...) or copy.deepcopy(). Frozen Pydantic for config/identity; MappingProxyType for non-Pydantic registries; deepcopy at system boundaries (tool execution, provider serialization, persistence).

Separate frozen config models from mutable-via-copy runtime models; never mix in one model.

Pydantic v2: ConfigDict(frozen=True, allow_inf_nan=False) everywhere; extra="forbid" on every model that doesn't round-trip through model_dump() (request DTOs always); @computed_field for derived values; NotBlankStr from core.types for identifier / name fields.

Args models at every system boundary (BaseTool, MCP tool, A2A RPC, WebSocket event): typed Pydantic args model validated before dispatch.

Typed-boundary helper: every dict ingestion from an external source (MCP args, JWT decode, WebSocket control, audit-chain payload, A2A JSON-RPC, settings security import) calls parse_typed() from synthorg.api.boundary with a hardcoded LiteralString boundary label.

Async concurrency: prefer asyncio.TaskGroup for fan-out / fan-in; wrap independent task bodies in async def helpers that catch Exception (re-raise only MemoryError / RecursionError).

Clock seam: classes that read time or sleep take clock: Clock | None = None (default SystemClock()); tests inject FakeClock.

Lifecycle sync: async start() / stop() services own a dedicated self._lifecycle_lock; timed-out stops mark the service unrestartable.

Untrusted-content fences (SEC-1): wrap attacker-controllable strings via wrap_untrusted() from synthorg.engine.prompt_safety; append untrusted_content_directive(tags).

HTML parsing (SEC-1): never call `lxml.html.f...

Files:

  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • src/synthorg/api/cursor.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/unit/api/controllers/test_setup.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/errors.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/engine/workflow/test_execution_service.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • tests/unit/api/controllers/test_setup_locales.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/client/factory.py
  • tests/unit/api/test_exception_handlers.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/meta/appliers/code_applier.py
  • tests/unit/meta/test_code_applier.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/meta/appliers/github_client.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/a2a/client.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/hr/errors.py
  • scripts/check_domain_error_hierarchy.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/settings/errors.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/backup/errors.py
  • src/synthorg/core/persistence_errors.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every Mock() / AsyncMock() / MagicMock() in tests/ MUST declare spec=ConcreteClass.

Use mock_dispatcher from tests/conftest.py (AsyncMock(spec=NotificationDispatcher)) for shared mocks.

Import FakeClock from tests._shared.fake_clock; inject via clock= parameter for time-driven tests. FakeClock.sleep advances virtual time and yields once via asyncio.sleep(0).

Never use real vendor names in test files; use test-provider, test-small-001.

Comments explain WHY only in test files; no review citations, issue back-refs, or migration framing.

Test markers: @pytest.mark.unit / integration / e2e / slow.

Async: asyncio_mode = "auto"; no manual @pytest.mark.asyncio.

Timeout: 30s per test (global in pyproject.toml); don't add per-file timeout(30) markers; non-default like timeout(60) is allowed.

Never monkeypatch.setattr(module.logger, "info", spy) for logger spying; the BoundLoggerLazyProxy caches the stale bound method via __dict__. Use try/finally del proxy.<level> instead.

Parametrize: prefer @pytest.mark.parametrize for similar cases.

Property-based: Hypothesis (Python), fast-check (React), testing.F (Go). CI runs 10 deterministic examples (derandomize=True). Hypothesis failures are real bugs: fix the bug and add an @example(...) decorator.

Flaky tests: NEVER skip/xfail/dismiss; fix fundamentally. FakeClock-first when the class accepts clock=. For "block until cancelled", use asyncio.Event().wait() not asyncio.sleep(large).

Files:

  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/meta/test_code_applier.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/meta/test_code_applier.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Handle errors explicitly, never swallow. Domain error families register a base-class entry in EXCEPTION_HANDLERS (src/synthorg/api/exception_handlers.py). Use <Domain><Condition>Error inheriting from DomainError; any of Exception / RuntimeError / LookupError / PermissionError / ValueError / TypeError / KeyError / IndexError / AttributeError / OSError / IOError as a direct base in src/synthorg/ is forbidden.

Every business-logic module: from synthorg.observability import get_logger then logger = get_logger(__name__). Variable name always logger. Carve-outs documented in module docstring.

Never import logging / logging.getLogger() / print() in application code (carve-out: observability/{setup,sinks,*_handler}.py for handler bootstrap).

Event names: import constants from synthorg.observability.events.<domain>; never string literals.

Structured kwargs: logger.info(EVENT, key=value); never logger.info("msg %s", val).

Error paths log at WARNING or ERROR with context before raising / returning.

State transitions log INFO via *_STATUS_TRANSITIONED constants (with from_status / to_status / domain id) AFTER the persistence write succeeds.

DEBUG for object creation, internal flow, key entry/exit. Pure data models, enums, re-exports skip logging.

Secret-log redaction (SEC-1): never call any logger severity with error=str(exc); use error_type=type(exc).__name__ and error=safe_error_description(exc).

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code/tests/comments/docstrings/configs. Use example-provider, example-{large,medium,small}-001. Allowed in: .claude/ files, third-party import paths, src/synthorg/providers/presets.py (user-facing runtime data), web/public/provider-logos/*.svg. Tests use test-provider, test-small-001.

Comments explain WHY only, never origin / review / issue context. Forbidden in source / tests / docstrings / commit bodies: review...

Files:

  • src/synthorg/api/cursor.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/errors.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/client/factory.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/a2a/client.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/hr/errors.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/settings/errors.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/backup/errors.py
  • src/synthorg/core/persistence_errors.py
src/synthorg/**/{api,controllers,services}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Controllers and API endpoints access persistence through domain-scoped service layers (e.g. ArtifactService, WorkflowService, MemoryService); services centralize audit logging; repositories must not log mutations themselves.

Files:

  • src/synthorg/api/cursor.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/auth/service.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

WebSocket per-frame timeout (DoS): silent peer closed with code 1008 after api.ws_frame_timeout_seconds (default 30s). Revalidation failures tracked via _SlidingWindowRateLimiter (api.ws_revalidation_window_seconds 60s, api.ws_revalidation_max_failures 5); saturation closes the socket with code 4011.

Files:

  • src/synthorg/api/cursor.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/auth/service.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/api/cursor.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/errors.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/client/factory.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/a2a/client.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/hr/errors.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/settings/errors.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/backup/errors.py
  • src/synthorg/core/persistence_errors.py
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use fenced code blocks with language tags: d2 for architecture / nested containers, mermaid for flowcharts / sequence / pipelines. Use markdown tables for tabular data; never use text fences with ASCII box-drawing.

Files:

  • docs/reference/errors.md
  • docs/reference/conventions.md
src/synthorg/persistence/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Datetime in persistence: parse_iso_utc / format_iso_utc from synthorg.persistence._shared (both reject naive); normalize_utc for relaxed coercion on already-typed datetime.

src/synthorg/persistence/ is the only place that may import aiosqlite / sqlite3 / psycopg / psycopg_pool or emit raw SQL DDL/DML. Every durable feature defines a Protocol in persistence/<domain>_protocol.py + concrete impls under persistence/{sqlite,postgres}/ exposed on PersistenceBackend.

Files:

  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/workflow_execution_repo.py
src/synthorg/meta/mcp/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Define ToolHandler in src/synthorg/meta/mcp/handlers/<domain>.py, declare args_model, call require_destructive_guardrails(arguments, actor) on any admin_tool, route through service-layer facades (never app_state.persistence.* directly), emit the three log paths via common_logging helpers.

Files:

  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/mcp/handlers/approvals.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Always read the relevant `docs/design/` page before implementing or planning. Deviations require explicit user approval; update the design page when an approved deviation lands. Never silently diverge.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding. Be critical at every phase; surface improvements as suggestions, not silent changes. Prioritize by dependency order, not priority labels.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: D2 diagram configuration uses theme 200 (Dark Mauve), dark-only, configured in `mkdocs.yml`; CI pins D2 CLI to v0.7.1 in `.github/workflows/pages.yml`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Validate at system boundaries (user input, external APIs, config files).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Never call `cd` in Bash commands; cwd is already project root. Exception: `bash -c "cd <dir> && <cmd>"` is safe (child process). Use this for tools without `-C`, e.g. `bash -c "cd web && npm install"`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Never use Bash to write files: use Write or Edit. Forbidden: `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)"`; read-only piping to stdout is fine.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Commits: `<type>: <description>` (feat/fix/refactor/docs/test/chore/perf/ci); enforced by commitizen.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Signed commits: required on every protected ref. GPG/SSH signed, OR GitHub App-signed via the `synthorg-repo-bot`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Branches: `<type>/<slug>` from main.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Merge strategy: squash. PR body becomes the squash commit message; trailers (`Release-As`, `Closes `#N``) must be in the PR body to land.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: After finishing an issue: branch (`<type>/<slug>`), commit, push. Do NOT auto-create a PR. ALWAYS use `/pre-pr-review` to create PRs (`gh pr create` is hookify-blocked). Trivial / docs-only: `/pre-pr-review quick`. After the PR exists, `/aurelio-review-pr` handles external reviewer feedback.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Fix EVERYTHING valid review agents find, including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes. No deferring, no "out of scope".
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: When tests fail due to timeout / slowness / xdist contention: NEVER delete, skip, or `xfail`; NEVER `--no-verify`; NEVER edit `tests/baselines/unit_timing.json`. Suite time exceeding `baseline * 1.3` is a source-code regression; fix the source, not the tests.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Coverage: 80% minimum (CI; benchmarks excluded).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Parallelism: `pytest-xdist -n 8 --dist=loadfile` (always). `loadfile` prevents the cumulative resource leak `worksteal` triggers on Python 3.14 + Windows ProactorEventLoop.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:05:49.459Z
Learning: Isolation regression gate: `scripts/run_affected_tests.py` re-runs the affected subset twice via `pytest-repeat --count 2 -x` after the green pass. Opt out via `SYNTHORG_SKIP_ISOLATION_GATE=1`.
🔇 Additional comments (49)
src/synthorg/meta/appliers/config_applier.py (1)

49-51: Looks good — explicit lint carve-out is correctly scoped.

The private _PathResolutionError keeps intentional stdlib inheritance and documents the exception to the hierarchy gate inline, which matches the rollout pattern.

src/synthorg/meta/mcp/handlers/approvals.py (2)

66-68: Handler-local not-found sentinel is correctly scoped and documented.

Using a private sentinel with explicit domain_code plus a per-line allowlist is appropriate here for MCP envelope mapping without HTTP-layer coupling.


80-82: Conflict sentinel change is clean and consistent with the MCP error-envelope design.

Private, handler-local conflict signaling with explicit lint allow is a good fit for this path.

src/synthorg/api/auth/ticket_store.py (2)

49-55: Domain-error migration is correctly implemented.

TicketLimitExceededError now inherits from PerOperationRateLimitError and declares the required ClassVar metadata for centralized RFC 9457 mapping.


173-177: 429 path is PII-safe and handler-friendly.

This keeps identifying detail in server logs while raising a typed domain error that can render a generic response from class defaults.

src/synthorg/meta/appliers/github_client.py (1)

28-58: Domain-error migration here looks correct and coherent.

The IntegrationError inheritance plus the github_status_code field separation and auth taxonomy ClassVars are aligned with centralized error mapping behavior.

tests/unit/meta/test_code_applier.py (1)

440-443: Assertion update is correct for the renamed exception field.

This test now validates the intended GitHubAPIError.github_status_code contract.

src/synthorg/backup/errors.py (1)

16-23: Good RFC 9457 metadata normalization across backup errors.

The ClassVar mappings on BackupError, BackupInProgressError, and BackupNotFoundError are clear and consistent with centralized domain-error dispatch.

Also applies to: 28-31, 53-56

src/synthorg/persistence/postgres/settings_repo.py (1)

34-36: LGTM — internal sentinel exception correctly renamed and scoped.

The renaming from _CASConflict to _CASConflictError is complete and consistent. The class correctly uses the stdlib Exception base with the required lint-allow comment, and the docstring accurately describes it as an internal sentinel that never escapes the repository. The CAS control-flow pattern (raise inside transaction → rollback → catch → return False) is correct.

Also applies to: 342-343, 363-364

src/synthorg/settings/errors.py (1)

9-34: DomainError metadata migration looks consistent.

SettingsError, SettingNotFoundError, and SettingValidationError now align with centralized RFC 9457-style mapping via ClassVar metadata and a DomainError root.

tests/unit/api/controllers/test_company.py (1)

109-113: 404 expectation is correct for this migrated error path.

Updating the assertion on Line 112 to 404 aligns this test with the SettingNotFoundError domain mapping.

src/synthorg/meta/rollout/inverse_dispatch.py (1)

31-33: Looks good — sentinel exception is explicitly allowlisted and unchanged semantically.

src/synthorg/persistence/workflow_execution_repo.py (1)

29-29: Docstring update is correct and aligned with the renamed persistence conflict error.

src/synthorg/meta/telemetry/emitter.py (1)

54-56: Approved — retry sentinel remains intentionally stdlib-rooted with explicit lint exception.

src/synthorg/tools/html_parse_guard.py (1)

73-75: Looks good — explicit allowlist marker is present for the internal XXE sentinel exception.

src/synthorg/api/controllers/role_versions.py (1)

18-19: Approved — not-found flow is correctly converted to NotFoundError with warning context prior to raising.

Also applies to: 94-96

src/synthorg/api/controllers/budget_config_versions.py (1)

19-20: Looks good — consistent migration to exception-based 404 handling via NotFoundError.

Also applies to: 95-97

src/synthorg/api/controllers/company_versions.py (1)

19-20: Approved — the not-found path now correctly relies on centralized domain exception handling.

Also applies to: 95-97

src/synthorg/observability/audit_chain/tsa_client.py (1)

71-73: Looks good — explicit lint allow is correctly attached to the internal TSA error root.

src/synthorg/api/controllers/evaluation_config_versions.py (1)

18-18: Controller not-found flow is correctly migrated to domain exceptions.

The warning log + NotFoundError raise path is consistent with centralized RFC 9457 handling and keeps the endpoint behavior clean.

Also applies to: 95-96

src/synthorg/meta/rollout/regression/welch.py (1)

30-32: Sentinel exception allowlist annotations look correct and explicit.

The inline lint-allow reasons are clear and keep the hierarchy gate intent auditable.

Also applies to: 36-38

src/synthorg/persistence/sqlite/backup_utils.py (1)

50-52: IntegrityCheckError sentinel declaration update is clean.

No behavioral drift, and the opt-out reason is appropriately documented on the class.

src/synthorg/meta/appliers/_validation.py (1)

25-27: DottedPathError gate opt-out is explicit and well-scoped.

The inline rationale is concise and keeps this internal precondition exception intentionally classified.

src/synthorg/security/trust/errors.py (1)

9-15: Trust error base migration is implemented correctly.

TrustError now conforms to the DomainError contract with the expected ClassVar taxonomy fields.

scripts/domain_error_hierarchy_baseline.txt (1)

1-10: Baseline file contract is clear and maintainable.

The frozen-format and regeneration guidance are documented well for future gate maintenance.

src/synthorg/telemetry/privacy.py (1)

124-127: PrivacyViolationError sentinel declaration is explicit and well-documented.

The class-level docstring plus targeted lint allow make the intent clear without changing runtime validation flow.

CLAUDE.md (1)

103-103: No action needed. The documentation in CLAUDE.md line 103 correctly reflects the actual hook configuration: domain-error-hierarchy is enforced at the pre-push stage only, as shown in .pre-commit-config.yaml (line 287: stages: [pre-push]). Acceptance criteria and docs are aligned.

src/synthorg/meta/appliers/code_applier.py (1)

46-59: PartialWriteError migration to typed domain error looks correct.

Class hierarchy and metadata are aligned with centralized domain-error handling.

src/synthorg/communication/event_stream/stream.py (1)

54-68: Lifecycle unrestartable error classification is correct.

Using ConflictError + 409 taxonomy cleanly matches the restart contract.

src/synthorg/api/auth/service.py (1)

34-40: SecretNotConfiguredError migration is consistent and well-scoped.

The 503-oriented domain-error metadata is properly declared for centralized mapping.

src/synthorg/a2a/client.py (1)

31-37: Outbound client error migration is aligned with the new convention.

DomainError base + provider-error metadata looks correct.

src/synthorg/meta/errors.py (1)

15-21: SelfImprovementError base-class migration is correct.

Metadata fields are present and consistent with centralized error rendering.

src/synthorg/client/factory.py (1)

84-90: Unknown strategy is now correctly modeled as a validation-domain error.

422/validation taxonomy here is a good fit for discriminator failures.

src/synthorg/engine/quality/decomposers/llm.py (1)

94-100: LLMDecompositionError conversion is clean and consistent.

No concerns with the new base class and taxonomy fields.

src/synthorg/core/registry/errors.py (1)

11-17: Registry error hierarchy updates look correct.

Both base and not-found variants now map cleanly through the domain error contract.

Also applies to: 43-46

src/synthorg/a2a/gateway.py (1)

617-635: Good migration of _A2AMethodError to DomainError with typed taxonomy metadata.

This keeps the local JSON-RPC behavior intact while making the class hierarchy compliant with the new domain-error contract.

src/synthorg/communication/mcp_errors.py (1)

17-30: CapabilityNotSupportedError now correctly participates in the domain-error hierarchy.

The typed class-level metadata is consistent and keeps MCP wire-level domain_code behavior explicit.

src/synthorg/hr/scaling/errors.py (1)

9-16: Scaling error family migration looks correct.

ScalingError is now properly rooted at DomainError, and ScalingCooldownActiveError has clear conflict-specific metadata.

Also applies to: 33-36

src/synthorg/meta/mcp/errors.py (1)

22-35: Nice cleanup on MCP handler errors.

Both classes now use typed domain_code and compliant domain-error ancestry (ValidationError / ForbiddenError) without changing constructor semantics.

Also applies to: 50-65

src/synthorg/engine/workflow/blueprint_errors.py (1)

9-24: Blueprint error classes now align cleanly with the domain taxonomy.

The inheritance and ClassVar mappings are consistent with expected not-found and validation semantics.

src/synthorg/telemetry/reporters/errors.py (1)

17-33: Telemetry reporter exceptions are now correctly domain-rooted.

Both classes include the required structured metadata and avoid forbidden stdlib direct bases.

src/synthorg/config/errors.py (1)

28-43: Config error hierarchy migration looks solid.

ConfigError/ConfigValidationError now expose the expected taxonomy metadata while preserving typed instance payload fields.

Also applies to: 90-96

src/synthorg/engine/workflow/execution_lifecycle.py (1)

16-16: Exception rename wiring looks correct.

The switch to PersistenceVersionConflictError is consistently applied in both import and retry handling.

Also applies to: 309-309

tests/unit/persistence/sqlite/test_workflow_execution_repo.py (1)

13-13: Test expectation update is consistent with the repository contract rename.

Also applies to: 157-158

tests/unit/engine/workflow/test_execution_lifecycle.py (1)

17-20: Fake repository conflict behavior stays in sync with production exception naming.

Also applies to: 92-93, 102-103

src/synthorg/persistence/postgres/workflow_definition_repo.py (1)

17-20: Postgres repository conflict-path rename is clean and consistent (code + docs).

Also applies to: 195-195, 273-273, 361-361

tests/unit/persistence/sqlite/test_workflow_definition_repo.py (1)

13-13: SQLite workflow-definition tests are correctly aligned to PersistenceVersionConflictError.

Also applies to: 184-185, 197-201, 210-211

tests/unit/api/fakes_workflow.py (1)

9-12: Fake workflow execution repository is correctly synced with the persistence conflict rename.

Also applies to: 79-80, 89-90

src/synthorg/engine/workflow/service.py (1)

48-64: Service-layer error taxonomy split is well implemented.

The domain-facing exceptions and persistence-conflict translation boundary are now consistently enforced.

Also applies to: 66-73, 297-308

Comment thread .pre-commit-config.yaml Outdated
Comment thread scripts/check_domain_error_hierarchy.py
Comment thread src/synthorg/api/cursor.py
Comment thread src/synthorg/api/services/workflow_rollback_service.py Outdated
Comment thread src/synthorg/engine/strategy/principles.py
Comment thread tests/unit/api/controllers/test_setup_locales.py
Comment thread tests/unit/api/controllers/test_setup.py
Comment thread tests/unit/api/controllers/test_workflow_versions.py Outdated
Comment thread tests/unit/api/test_exception_handlers.py Outdated
Comment thread tests/unit/scripts/test_check_domain_error_hierarchy.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 91.64835% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.75%. Comparing base (0a46e19) to head (3634c39).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...rc/synthorg/engine/workflow/execution_lifecycle.py 25.00% 27 Missing ⚠️
src/synthorg/api/controllers/workflow_versions.py 80.95% 4 Missing ⚠️
...rc/synthorg/api/controllers/workflow_executions.py 66.66% 1 Missing ⚠️
src/synthorg/api/controllers/workflows.py 0.00% 1 Missing ⚠️
src/synthorg/engine/strategy/principles.py 93.33% 1 Missing ⚠️
src/synthorg/engine/workflow/service.py 93.75% 1 Missing ⚠️
src/synthorg/persistence/postgres/settings_repo.py 75.00% 1 Missing ⚠️
...g/persistence/postgres/workflow_definition_repo.py 66.66% 1 Missing ⚠️
...org/persistence/sqlite/workflow_definition_repo.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1756      +/-   ##
==========================================
+ Coverage   84.71%   84.75%   +0.04%     
==========================================
  Files        1794     1794              
  Lines      102755   103069     +314     
  Branches     9021     9021              
==========================================
+ Hits        87047    87355     +308     
- Misses      13523    13530       +7     
+ Partials     2185     2184       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Aureliolo added 3 commits May 4, 2026 22:37
Migrate every plain-Exception class in src/synthorg/ to a DomainError-rooted hierarchy and ship scripts/check_domain_error_hierarchy.py to prevent regression. The gate fails closed: any new class with a direct Exception/RuntimeError/LookupError/PermissionError/ValueError/etc. base must either reach DomainError or carry a per-line '# lint-allow: domain-error-hierarchy -- <reason>' opt-out.

Migrations cover backup, config, hr (+ scaling), security/trust, settings, core/registry, templates, meta/mcp argument-validation + guardrail-violation, core/persistence_errors (with VersionConflictError -> PersistenceVersionConflictError rename to dispel the name collision with domain_errors.VersionConflictError), the 5 version-controllers (Budget/Company/EvaluationConfig/Role/Workflow switch from Response(status_code=...) returns to raising NotFoundError/ValidationError/VersionConflictError so the centralised RFC 9457 dispatch handles them), and ~16 scattered classes across a2a, api/auth, api/cursor, communication/*, engine/quality/decomposers/llm, engine/workflow/blueprint_errors, engine/workflow/service, engine/strategy/principles, meta/*, telemetry/*, persistence/*.

handle_backup_error is removed; handle_domain_error reads ClassVars from each migrated class. 7 internal-sentinel classes carry per-line allowlist markers (TsaError, MCP handler-local _NotFoundError/_ConflictError, statistical Welch errors, etc.) where stdlib bases are deliberate per the docstrings.
Triage from 14 review agents flagged 12 valid findings: 2 CRITICAL + 5 MAJOR + 3 MEDIUM + 2 MINOR/INFO. All 12 implemented.

CRITICAL: TicketLimitExceededError no longer leaks user_id via str(exc) on the 429 response (the message is logged server-side and the class default_message provides the user-safe fallback). CLAUDE.md forbidden-bases bullet now lists the full 11-base set the gate enforces.

MAJOR: comment-quality-rot strips 'Issue #1738.' from the gate script + baseline header (issue refs belong in PR body, not source). workflow_rollback_service docstrings reference the renamed PersistenceVersionConflictError. The diff_same_version test asserts the full RFC 9457 envelope (error_code, error_category, retryable). New TestMigratedDomainErrorFamilies parametrized class covers 14 ClassVar->HTTP mappings (HR/Scaling/Trust/Settings/Config/registry families). New TestRollback test pins the PersistenceVersionConflictError->VersionConflictError translation in workflow_versions controller.

MEDIUM: ConfigError + ConfigValidationError declare instance attrs in class body for mypy/IDE visibility. ConstraintViolationError docstring explains the deliberate 400 (vs 422) status_code, matching handle_persistence_integrity_error.

MINOR/INFO: WorkflowDefinitionRevisionMismatchError docstring notes the inherited 409. conventions.md section 6 cross-links the new gate. _resolve_base docstring documents the deeply-chained-attribute edge case.
Addresses 14 valid findings from this round (1 Gemini, 13 CodeRabbit
including 4 outside-diff and the parallel adjacent fix to
PruningUnrestartableError).

Persistence layer:
- SQLite + Postgres workflow_execution_repo: probe with SELECT after
  rowcount=0 to differentiate delete-race (RecordNotFoundError) from
  true version mismatch (PersistenceVersionConflictError); align
  protocol Raises docstring; add unit + conformance tests for the
  delete-race path.
- workflow_executions controller: catch RecordNotFoundError in cancel
  flow so a delete race produces a 404 instead of falling into the
  generic 500 path.
- execution_lifecycle.handle_task_state_changed: treat
  RecordNotFoundError as terminal (retry would only fail the same
  way); extract _retry_event_for + _dispatch_task_handler helpers to
  keep the function under the C901 threshold.

Domain-error hierarchy:
- BackupUnrestartableError: re-rooted under BackupError so
  ``except BackupError`` catches it; explicit 409 ClassVar metadata.
- PruningUnrestartableError (adjacent fix): re-rooted under
  PruningError so the HRError module-level retryability contract
  holds; explicit 409 ClassVar metadata.
- HRError business-state subclasses (HiringApprovalRequiredError,
  HiringRejectedError, PromotionApprovalRequiredError) now declare
  explicit 4xx ClassVars instead of inheriting the 500 default.
- check_domain_error_hierarchy.py: rooted fast-path now runs
  _has_forbidden_direct_base before the rooted membership check so
  mixed inheritance (DomainError, ValueError) cannot bypass the
  gate; alias resolver builds (head, None) for non-aliased
  ``import a.b.c`` so cross-module attribute paths resolve to
  ``a.b.c.X`` instead of ``a.b.c.b.c.X``.
- pre-commit hook: domain-error-hierarchy now runs at both
  pre-commit and pre-push so violations surface at commit time.

Other:
- InvalidCursorError docstring: HTTP 400 -> 422 to match
  status_code ClassVar.
- WorkflowRollbackService docstring: trim controller/HTTP-translation
  narrative; keep layer-local contract only.
- principles.load_and_merge: log STRATEGY_PACK_INVALID at WARNING
  (with safe_error_description) before raising
  StrategyPackValidationError.
- code_applier._write_changes Raises: RuntimeError -> PartialWriteError.
- AsyncMock spec= on the three settings_svc.get_entry replacements in
  test_setup.py / test_setup_locales.py; mock_spec_baseline
  regenerated (shrunk by 6 entries).
- test_workflow_versions: hoist block-local imports to module scope
  so test_rollback_persistence_version_conflict_translates_to_409
  fits under the 50-line limit.
- test_exception_handlers: strip migration framing from
  TestDomainErrorFamilyClassVarHttpMapping (renamed) docstring +
  comment.
- test_check_domain_error_hierarchy: monkeypatch.chdir(tmp_path.parent)
  in three CLI tests so a regression that ignored --repo-root and
  used cwd would actually fail.

Skipped (factually wrong against current code):
- gemini #339 PEP 758 except syntax: project mandates the
  ``except A, B:`` form; AST parses to Tuple, not as-bind. Verified
  on Python 3.14.4.
- gemini #334 / #345 git ls-files non-recursive glob: empirical
  test shows ``git ls-files -z -- 'src/synthorg/*.py'`` returns 1873
  files including 4-level-deep paths.
@Aureliolo Aureliolo force-pushed the chore/audit-followup-c-1738 branch from 3bf01ae to ec8cbb2 Compare May 4, 2026 20:47
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview May 4, 2026 20:49 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/synthorg/tools/html_parse_guard.py (1)

78-80: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Docstring describes the wrong catch path for XXEDetectedError.

The class docstring says this is caught by the generic except Exception branch, but sanitize() handles it explicitly at Line 229. Please align the docstring with the current control flow to avoid future misreads.

Suggested patch
-    Subclass of ``ValueError`` so the ``except Exception`` branch in
-    :meth:`HTMLParseGuard.sanitize` catches it naturally and routes
-    to the safe-empty fallback.
+    Subclass of ``ValueError`` caught explicitly by
+    :meth:`HTMLParseGuard.sanitize`, which routes XXE payloads
+    to the safe-empty fallback.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/tools/html_parse_guard.py` around lines 78 - 80, Update the
XXEDetectedError class docstring to reflect that it is explicitly handled by
HTMLParseGuard.sanitize (not merely by a generic except Exception), i.e., state
that sanitize() checks for and catches XXEDetectedError and routes to the
safe-empty fallback; reference XXEDetectedError and HTMLParseGuard.sanitize in
the docstring so future readers see the exact control flow and outcome.
src/synthorg/config/errors.py (2)

78-79: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ConfigParseError should define validation-category metadata.

A YAML parse failure is a malformed-input condition (bad request / validation error), not an internal server error. Without explicit metadata it inherits 500, which misrepresents the error to callers.

🔧 Proposed fix
 class ConfigParseError(ConfigError):
     """Raised when YAML parsing fails."""
+
+    default_message: ClassVar[str] = "Configuration file parse error"
+    error_category: ClassVar[ErrorCategory] = ErrorCategory.VALIDATION
+    error_code: ClassVar[ErrorCode] = ErrorCode.VALIDATION_ERROR
+    status_code: ClassVar[int] = 400
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/config/errors.py` around lines 78 - 79, ConfigParseError
currently lacks validation metadata and therefore inherits a 500 internal error;
update the ConfigParseError class (subclass of ConfigError) to explicitly mark
it as a validation/malformed-input error (e.g., set an attribute like
error_category = "validation" or status_code = 400, or use the project's
ErrorCategory enum) so YAML parse failures are treated as client/validation
errors rather than internal server errors. Ensure the attribute name and value
follow the project's existing error-metadata convention used elsewhere in
ConfigError subclasses.

74-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ConfigFileNotFoundError inherits a 500 status but semantically represents a 404.

This error indicates a configuration file does not exist—a "not found" condition that should map to HTTP 404, not the inherited 500. Without explicit metadata, the API layer will return an opaque 500 instead of a structured 404.

🔧 Proposed fix
 class ConfigFileNotFoundError(ConfigError):
     """Raised when a configuration file does not exist."""
+
+    default_message: ClassVar[str] = "Configuration file not found"
+    error_category: ClassVar[ErrorCategory] = ErrorCategory.NOT_FOUND
+    error_code: ClassVar[ErrorCode] = ErrorCode.RESOURCE_NOT_FOUND
+    status_code: ClassVar[int] = 404
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/config/errors.py` around lines 74 - 75, Change
ConfigFileNotFoundError so it maps to HTTP 404 instead of inheriting the default
500: update the class definition for ConfigFileNotFoundError to include the same
metadata/attribute used by other errors for status codes (e.g., add status_code
= 404 or http_status = 404 or override the to_response metadata) so the API
layer returns a structured 404; reference the ConfigFileNotFoundError class and
mirror how other error classes in the module set their HTTP status attribute.
src/synthorg/engine/workflow/execution_lifecycle.py (1)

338-362: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the version-conflict retry idempotent.

After a conflict, this branch re-dispatches the event against the refreshed execution without checking whether the winning writer already applied the same node transition. A duplicate COMPLETED event on a still-RUNNING execution will therefore save the same node state again, bump version, and emit a bogus TASK_COMPLETED -> TASK_COMPLETED transition. Short-circuit when the refreshed node already matches the terminal node status implied by event.new_status before calling _dispatch_task_handler() again.

Suggested guard
     except PersistenceVersionConflictError:
         retry_event = _retry_event_for(event)
         logger.warning(
             retry_event,
             execution_id=execution.id,
@@
         if refreshed.status in {
             WorkflowExecutionStatus.COMPLETED,
             WorkflowExecutionStatus.FAILED,
             WorkflowExecutionStatus.CANCELLED,
         }:
             return
+        expected_node_status = (
+            WorkflowNodeExecutionStatus.TASK_FAILED
+            if event.new_status in {TaskStatus.FAILED, TaskStatus.CANCELLED}
+            else WorkflowNodeExecutionStatus.TASK_COMPLETED
+        )
+        if _node_status_for_task(refreshed, event.task_id) is expected_node_status:
+            return
         try:
             await _dispatch_task_handler(repo, refreshed, event)
         except Exception:
             logger.exception(
                 retry_event,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/synthorg/engine/workflow/execution_lifecycle.py` around lines 338 - 362,
After catching PersistenceVersionConflictError, fetch the refreshed execution
(repo.get(execution.id)) and before calling _dispatch_task_handler(check
_retry_event_for(event) and logging as-is), short-circuit if the refreshed
execution already reflects the node transition implied by the incoming event:
compare the refreshed execution's node/task status for event.task_id (or the
node state that event.new_status targets) against event.new_status (and/or
whether it is already in the corresponding terminal set) and return early to
avoid re-applying the same terminal transition; keep the existing logger.warning
and only call _dispatch_task_handler(refreshed, event) when the refreshed state
needs the transition.
tests/unit/api/controllers/test_company.py (1)

99-112: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a spec to this async override.

This AsyncMock drops the repo-wide spec-locking rule, so the test won’t catch signature or awaitability drift on config_resolver.get_str.

Minimal fix
         with TestClient(app) as client:
             client.headers.update(make_auth_headers("ceo"))
             resolver = app.state.app_state.config_resolver
+            original_get_str = resolver.get_str
             resolver.get_str = AsyncMock(
+                spec=original_get_str,
                 side_effect=SettingNotFoundError("company/company_name"),
             )
             resp = client.get("/api/v1/company")
             assert resp.status_code == 404

As per coding guidelines, tests/**/*.py: Every Mock() / AsyncMock() / MagicMock() in tests/ MUST declare spec=ConcreteClass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/api/controllers/test_company.py` around lines 99 - 112, The
AsyncMock used to override resolver.get_str should declare a spec so the test
enforces the real method signature; replace the current AsyncMock(...) with one
that sets spec to the real method (e.g. AsyncMock(spec=type(resolver).get_str,
side_effect=SettingNotFoundError("company/company_name"))) and assign it back to
resolver.get_str so the mock is signature- and awaitability-checked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/synthorg/a2a/client.py`:
- Around line 31-37: Register the new A2AClientError in the global exception
mapping by adding the tuple (A2AClientError, handle_domain_error) to
EXCEPTION_HANDLERS so it is handled like other DomainError subclasses; locate
EXCEPTION_HANDLERS in the module that defines exception handlers (where
CommunicationError, IntegrationError, ToolError are already registered) and
append the entry ensuring import of A2AClientError if not already present.

In `@src/synthorg/api/controllers/workflows.py`:
- Line 35: The controller is catching PersistenceVersionConflictError which is
no longer raised by WorkflowService.update_definition(); replace that catch with
the new WorkflowDefinitionRevisionMismatchError (update the import to import
WorkflowDefinitionRevisionMismatchError and remove
PersistenceVersionConflictError) so the 409/logging branch is executed on
optimistic-concurrency conflicts; make the same substitution in the other
handler block referenced (the one around lines 470-482) to ensure both
controller paths handle WorkflowDefinitionRevisionMismatchError correctly.

In `@tests/unit/engine/workflow/test_execution_lifecycle.py`:
- Around line 17-20: The fake repository used in these tests must mirror the
real repo's save() contract: when save() is called with version > 1 for a row
that no longer exists, raise RecordNotFoundError (not
PersistenceVersionConflictError). Update the fake's save() implementation(s)
(the test fake referenced here and the similar one at lines ~87-103) to raise
synthorg.core.persistence_errors.RecordNotFoundError on the deleted-row /
version>1 path, leaving other error paths (e.g., duplicate) unchanged so
handle_task_state_changed() can exercise the delete-race branch correctly.

---

Outside diff comments:
In `@src/synthorg/config/errors.py`:
- Around line 78-79: ConfigParseError currently lacks validation metadata and
therefore inherits a 500 internal error; update the ConfigParseError class
(subclass of ConfigError) to explicitly mark it as a validation/malformed-input
error (e.g., set an attribute like error_category = "validation" or status_code
= 400, or use the project's ErrorCategory enum) so YAML parse failures are
treated as client/validation errors rather than internal server errors. Ensure
the attribute name and value follow the project's existing error-metadata
convention used elsewhere in ConfigError subclasses.
- Around line 74-75: Change ConfigFileNotFoundError so it maps to HTTP 404
instead of inheriting the default 500: update the class definition for
ConfigFileNotFoundError to include the same metadata/attribute used by other
errors for status codes (e.g., add status_code = 404 or http_status = 404 or
override the to_response metadata) so the API layer returns a structured 404;
reference the ConfigFileNotFoundError class and mirror how other error classes
in the module set their HTTP status attribute.

In `@src/synthorg/engine/workflow/execution_lifecycle.py`:
- Around line 338-362: After catching PersistenceVersionConflictError, fetch the
refreshed execution (repo.get(execution.id)) and before calling
_dispatch_task_handler(check _retry_event_for(event) and logging as-is),
short-circuit if the refreshed execution already reflects the node transition
implied by the incoming event: compare the refreshed execution's node/task
status for event.task_id (or the node state that event.new_status targets)
against event.new_status (and/or whether it is already in the corresponding
terminal set) and return early to avoid re-applying the same terminal
transition; keep the existing logger.warning and only call
_dispatch_task_handler(refreshed, event) when the refreshed state needs the
transition.

In `@src/synthorg/tools/html_parse_guard.py`:
- Around line 78-80: Update the XXEDetectedError class docstring to reflect that
it is explicitly handled by HTMLParseGuard.sanitize (not merely by a generic
except Exception), i.e., state that sanitize() checks for and catches
XXEDetectedError and routes to the safe-empty fallback; reference
XXEDetectedError and HTMLParseGuard.sanitize in the docstring so future readers
see the exact control flow and outcome.

In `@tests/unit/api/controllers/test_company.py`:
- Around line 99-112: The AsyncMock used to override resolver.get_str should
declare a spec so the test enforces the real method signature; replace the
current AsyncMock(...) with one that sets spec to the real method (e.g.
AsyncMock(spec=type(resolver).get_str,
side_effect=SettingNotFoundError("company/company_name"))) and assign it back to
resolver.get_str so the mock is signature- and awaitability-checked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9321c319-10a0-42b9-b5a3-7ecc8598ab7e

📥 Commits

Reviewing files that changed from the base of the PR and between 3bf01ae and ec8cbb2.

📒 Files selected for processing (79)
  • .pre-commit-config.yaml
  • CLAUDE.md
  • docs/reference/conventions.md
  • docs/reference/errors.md
  • scripts/check_domain_error_hierarchy.py
  • scripts/domain_error_hierarchy_baseline.txt
  • scripts/mock_spec_baseline.txt
  • src/synthorg/a2a/client.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/cursor.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/backup/errors.py
  • src/synthorg/client/factory.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/config/errors.py
  • src/synthorg/core/persistence_errors.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/hr/errors.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/meta/errors.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/settings/errors.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/tools/html_parse_guard.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/unit/meta/test_code_applier.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
💤 Files with no reviewable changes (2)
  • scripts/mock_spec_baseline.txt
  • src/synthorg/api/exception_handlers.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Lighthouse Site
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (17)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use Python 3.14+ with PEP 649 native lazy annotations (no from __future__ import annotations).

Run uv run ruff check src/ tests/ --fix for linting with auto-fix.

Run uv run mypy src/ tests/ for strict type-checking on all Python code.

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • tests/unit/meta/test_code_applier.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • tests/unit/core/test_persistence_errors.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • tests/unit/api/controllers/test_company.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • tests/unit/engine/workflow/test_execution_service.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • tests/unit/api/controllers/test_setup.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • tests/unit/api/controllers/test_setup_locales.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • scripts/check_domain_error_hierarchy.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py
{**/*.py,web/src/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

No default may privilege a region, currency, or locale. Use DEFAULT_CURRENCY from synthorg.budget.currency backend; metric units only; International/British English UI default.

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • tests/unit/meta/test_code_applier.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • tests/unit/core/test_persistence_errors.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • tests/unit/api/controllers/test_company.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • tests/unit/engine/workflow/test_execution_service.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • tests/unit/api/controllers/test_setup.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • tests/unit/api/controllers/test_setup_locales.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • scripts/check_domain_error_hierarchy.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Controllers and API endpoints access persistence through domain-scoped service layers (e.g. ArtifactService, WorkflowService, MemoryService); services centralize audit logging.

Direct os.environ.get(...) outside startup is forbidden.

Any of Exception, RuntimeError, LookupError, PermissionError, ValueError, TypeError, KeyError, IndexError, AttributeError, OSError, IOError as a direct base in src/synthorg/ is forbidden.

Every business-logic module: from synthorg.observability import get_logger then logger = get_logger(__name__). Variable name always logger.

Never import logging / logging.getLogger() / print() in application code (carve-out: observability/{setup,sinks,*_handler}.py for handler bootstrap).

Import event name constants from synthorg.observability.events.<domain>; never string literals.

Structured logging: logger.info(EVENT, key=value); never logger.info("msg %s", val).

State transitions log INFO via *_STATUS_TRANSITIONED constants AFTER the persistence write succeeds.

DEBUG for object creation, internal flow, key entry/exit. Pure data models, enums, re-exports skip logging.

Never call logger with error=str(exc); use error_type=type(exc).__name__ and error=safe_error_description(exc).

Telemetry is opt-in, off by default. Every event property must be in _ALLOWED_PROPERTIES keyed by event type; unknown keys raise PrivacyViolationError.

All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry in driver subclasses.

RetryConfig / RateLimiterConfig set per-provider in ProviderConfig. Retryable: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError.

Rate limiter respects RateLimitError.retry_after.

Pluggable subsystems: protocol + strategy + factory + config discriminator with safe defaults. Services (which wrap repositories) are a distinct pattern.

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py
{src/**/*.py,web/src/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

Comments explain WHY only, never origin/review/issue context. Forbidden: reviewer citations, in-code issue back-refs, naked SEC-1 taxonomy, migration framing, round narrative.

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Type hints on all public functions; mypy strict.

Docstrings: Google style on public classes/functions (ruff D rules).

Never mutate; create new objects via model_copy(update=...) or copy.deepcopy(). Frozen Pydantic for config/identity.

Separate frozen config models from mutable-via-copy runtime models; never mix in one model.

Pydantic v2: ConfigDict(frozen=True, allow_inf_nan=False) everywhere; extra="forbid" on every model that doesn't round-trip through model_dump() (request DTOs always).

Use NotBlankStr from core.types for identifier/name fields.

@computed_field for derived values in Pydantic models.

Args models at every system boundary (BaseTool, MCP tool, A2A RPC, WebSocket event): typed Pydantic args model validated before dispatch.

Every dict ingestion from an external source calls parse_typed() from synthorg.api.boundary with a hardcoded LiteralString boundary label.

Prefer asyncio.TaskGroup for fan-out/fan-in; wrap independent task bodies in async def helpers that catch Exception.

Classes that read time or sleep take clock: Clock | None = None (default SystemClock()); tests inject FakeClock.

Async start() / stop() services own a dedicated self._lifecycle_lock; timed-out stops mark the service unrestartable.

Wrap attacker-controllable strings via wrap_untrusted() from synthorg.engine.prompt_safety; append untrusted_content_directive(tags).

Never call lxml.html.fromstring on attacker input; use HTMLParseGuard.

Line length 88 (ruff); functions <50 lines; files <800 lines.

Domain error families register a base-class entry in EXCEPTION_HANDLERS (src/synthorg/api/exception_handlers.py). Use <Domain><Condition>Error inheriting from DomainError.

Repository CRUD: save(entity) -> None (idempotent), get(id) -> Entity | None, delete(id) -> bool, list_items(...) -> tuple[Entity, ...], query(...) -> tuple[Entity, ...]. Query methods always return tuples.

PEP 758 except: except A, B:...

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py
{src/**/*.py,tests/**/*.py,**/*.{yaml,yml,json}}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code/tests/comments/docstrings/configs. Use example-provider, example-{large,medium,small}-001.

Files:

  • src/synthorg/meta/appliers/_validation.py
  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • tests/unit/meta/test_code_applier.py
  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/appliers/config_applier.py
  • src/synthorg/telemetry/privacy.py
  • src/synthorg/meta/rollout/regression/welch.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • tests/unit/core/test_persistence_errors.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • tests/unit/api/controllers/test_company.py
  • src/synthorg/meta/errors.py
  • src/synthorg/security/trust/errors.py
  • src/synthorg/telemetry/reporters/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • src/synthorg/observability/audit_chain/tsa_client.py
  • src/synthorg/hr/scaling/errors.py
  • tests/unit/engine/workflow/test_execution_service.py
  • src/synthorg/meta/rollout/inverse_dispatch.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/meta/telemetry/emitter.py
  • src/synthorg/client/factory.py
  • src/synthorg/api/cursor.py
  • tests/unit/api/controllers/test_setup.py
  • src/synthorg/core/registry/errors.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/engine/quality/decomposers/llm.py
  • src/synthorg/api/auth/ticket_store.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • tests/unit/api/controllers/test_setup_locales.py
  • src/synthorg/engine/workflow/blueprint_errors.py
  • src/synthorg/communication/mcp_errors.py
  • src/synthorg/communication/event_stream/stream.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • src/synthorg/config/errors.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/engine/strategy/principles.py
  • src/synthorg/backup/errors.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/a2a/client.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • src/synthorg/meta/mcp/errors.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/communication/meeting/agent_caller.py
  • src/synthorg/engine/workflow/service.py
  • src/synthorg/api/controllers/budget_config_versions.py
  • src/synthorg/persistence/sqlite/backup_utils.py
  • src/synthorg/meta/appliers/code_applier.py
  • src/synthorg/hr/errors.py
  • src/synthorg/a2a/gateway.py
  • src/synthorg/core/persistence_errors.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
  • src/synthorg/meta/appliers/github_client.py
  • src/synthorg/settings/errors.py
src/synthorg/persistence/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/persistence/ is the only place that may import aiosqlite, sqlite3, psycopg, psycopg_pool or emit raw SQL DDL/DML.

Every durable feature defines a Protocol in persistence/<domain>_protocol.py + concrete impls under persistence/{sqlite,postgres}/.

Datetime in persistence: parse_iso_utc / format_iso_utc from synthorg.persistence._shared (both reject naive); normalize_utc for relaxed coercion.

Files:

  • src/synthorg/persistence/sqlite/workflow_definition_repo.py
  • src/synthorg/persistence/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/settings_repo.py
  • src/synthorg/persistence/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/refresh_repo.py
  • src/synthorg/persistence/postgres/workflow_execution_repo.py
  • src/synthorg/persistence/postgres/workflow_definition_repo.py
  • src/synthorg/persistence/sqlite/backup_utils.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER delete, skip, or xfail flaky tests; NEVER --no-verify; NEVER edit tests/baselines/unit_timing.json.

Test markers: @pytest.mark.unit / integration / e2e / slow.

Every Mock() / AsyncMock() / MagicMock() in tests/ MUST declare spec=ConcreteClass.

Use mock_dispatcher from tests/conftest.py (AsyncMock(spec=NotificationDispatcher)).

Time-driven tests: import FakeClock from tests._shared.fake_clock; inject via clock= parameter.

Async: asyncio_mode = "auto"; no manual @pytest.mark.asyncio.

Never monkeypatch.setattr(module.logger, "info", spy) because BoundLoggerLazyProxy caches via __dict__. Use try/finally del proxy.<level> instead.

Prefer @pytest.mark.parametrize for similar test cases.

Tests use test-provider, test-small-001.

Hypothesis failures are real bugs: fix the bug and add an @example(...) decorator.

Flaky tests: NEVER skip/xfail/dismiss; fix fundamentally. FakeClock-first when the class accepts clock=. For "block until cancelled", use asyncio.Event().wait() not asyncio.sleep(large).

Files:

  • tests/unit/meta/test_code_applier.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/meta/test_code_applier.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
{pyproject.toml,tests/**/*.py}

📄 CodeRabbit inference engine (CLAUDE.md)

Timeout: 30s per test (global in pyproject.toml); non-default like timeout(60) is allowed.

Files:

  • tests/unit/meta/test_code_applier.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
{tests/**/*.py,web/**/*.test.{ts,tsx},cli/**/*_test.go}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing: Hypothesis (Python), fast-check (React), testing.F (Go). CI runs 10 deterministic examples (derandomize=True).

Files:

  • tests/unit/meta/test_code_applier.py
  • tests/unit/communication/meeting/test_agent_caller.py
  • tests/unit/core/test_persistence_errors.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/api/services/test_workflow_rollback_service.py
  • tests/unit/engine/workflow/test_execution_service.py
  • tests/conformance/persistence/test_workflow_execution_repository.py
  • tests/unit/api/controllers/test_setup.py
  • tests/unit/api/test_exception_handlers.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_setup_locales.py
  • tests/unit/persistence/sqlite/test_workflow_definition_repo.py
  • tests/conformance/persistence/test_workflow_definition_repository.py
  • tests/unit/persistence/sqlite/test_workflow_execution_repo.py
  • tests/unit/api/fakes_workflow.py
  • tests/unit/api/controllers/test_workflow_versions.py
  • tests/unit/scripts/test_check_domain_error_hierarchy.py
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use fenced code blocks with language tags: d2 for architecture/nested containers, mermaid for flowcharts/sequence/pipelines. Use markdown tables for tabular data; never use text fences with ASCII box-drawing.

Files:

  • CLAUDE.md
  • docs/reference/conventions.md
  • docs/reference/errors.md
.pre-commit-config.yaml

📄 CodeRabbit inference engine (CLAUDE.md)

Wire each new convention gate into .pre-commit-config.yaml so it runs locally and in CI.

Pre-commit hooks: ruff, gitleaks, hadolint, no-em-dashes, no-redundant-timeout, check-single-migration-per-pr, check-no-modify-migration.

Files:

  • .pre-commit-config.yaml
{.pre-commit-config.yaml,web/**}

📄 CodeRabbit inference engine (CLAUDE.md)

eslint-web runs at pre-push only.

Files:

  • .pre-commit-config.yaml
src/synthorg/meta/mcp/handlers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Define ToolHandler in src/synthorg/meta/mcp/handlers/<domain>.py; declare args_model, call require_destructive_guardrails(arguments, actor) on any admin_tool.

Files:

  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/mcp/handlers/approvals.py
src/synthorg/meta/mcp/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Route through service-layer facades (never app_state.persistence.* directly); emit three log paths via common_logging helpers.

Files:

  • src/synthorg/meta/mcp/handlers/budget.py
  • src/synthorg/meta/mcp/handlers/approvals.py
  • src/synthorg/meta/mcp/errors.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

WebSocket per-frame timeout (DoS): silent peer closed with code 1008 after api.ws_frame_timeout_seconds (default 30s).

Files:

  • src/synthorg/api/controllers/evaluation_config_versions.py
  • src/synthorg/api/cursor.py
  • src/synthorg/api/controllers/company_versions.py
  • src/synthorg/api/auth/ticket_store.py
  • src/synthorg/api/controllers/workflow_executions.py
  • src/synthorg/api/services/workflow_rollback_service.py
  • src/synthorg/api/controllers/role_versions.py
  • src/synthorg/api/auth/service.py
  • src/synthorg/api/controllers/workflow_versions.py
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/controllers/budget_config_versions.py
src/synthorg/settings/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

For every mutable setting: DB > env > YAML > code default, resolved through SettingsService / ConfigResolver. Register new settings in src/synthorg/settings/definitions/<namespace>.py.

Files:

  • src/synthorg/settings/errors.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Always read the relevant `docs/design/` page before implementing or planning. Deviations require explicit user approval; update the design page when an approved deviation lands.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: License: BUSL-1.1 with narrowed Additional Use Grant; converts to Apache 2.0 three years after release.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Project layout: `src/synthorg/` (src layout), `tests/`, `web/` (React 19 dashboard), `cli/` (Go CLI binary).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Use `uv sync` for all dependencies, `uv sync --group docs` for docs toolchain.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Run pytest with `-n 8` for parallelism using `--dist=loadfile` to prevent resource leaks on Python 3.14 + Windows ProactorEventLoop.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Any PR establishing or expanding a project-wide convention MUST include the AST/script gate that prevents regression. PRs proposing a convention without enforcement are rejected.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Validate at system boundaries (user input, external APIs, config files).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Suite time exceeding baseline * 1.3 is a source-code regression; fix the source, not the tests.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Coverage: 80% minimum (CI; benchmarks excluded).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Parallelism: `pytest-xdist -n 8 --dist=loadfile` (always). `loadfile` prevents the cumulative resource leak `worksteal` triggers on Python 3.14 + Windows ProactorEventLoop.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Isolation regression gate: `scripts/run_affected_tests.py` re-runs affected subset twice via `pytest-repeat --count 2 -x` after green pass.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Commits: `<type>: <description>` (feat/fix/refactor/docs/test/chore/perf/ci); enforced by commitizen.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Signed commits: required on every protected ref. GPG/SSH signed, OR GitHub App-signed via the `synthorg-repo-bot`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Branches: `<type>/<slug>` from main.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Merge strategy: squash. PR body becomes the squash commit message; trailers (`Release-As`, `Closes `#N``) must be in the PR body.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: PR issue references: preserve existing `Closes `#NNN``; never remove unless explicitly asked.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: After finishing an issue: branch (`<type>/<slug>`), commit, push. Do NOT auto-create a PR.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: ALWAYS use `/pre-pr-review` to create PRs (`gh pr create` is hookify-blocked). Trivial/docs-only: `/pre-pr-review quick`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: After the PR exists, `/aurelio-review-pr` handles external reviewer feedback.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T20:49:10.259Z
Learning: Fix EVERYTHING valid review agents find, including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes.

Comment thread src/synthorg/a2a/client.py
Comment thread src/synthorg/api/controllers/workflows.py Outdated
Comment thread tests/unit/engine/workflow/test_execution_lifecycle.py
Addresses 8 valid findings from CodeRabbit's re-review on the
ec8cbb2 head (3 inline + 5 outside-diff).

Domain-error wiring:
- Register A2AClientError in EXCEPTION_HANDLERS so the outbound
  A2A client error gets routed by handle_domain_error like the
  other domain families (CommunicationError / IntegrationError /
  ToolError).
- ConfigFileNotFoundError: explicit 404 / NOT_FOUND ClassVars
  (was inheriting 500 from ConfigError).
- ConfigParseError: explicit 400 / VALIDATION ClassVars (YAML parse
  failure is a malformed-input condition, not an internal error).

Controllers + lifecycle:
- workflows.py update_workflow now catches the translated
  WorkflowDefinitionRevisionMismatchError instead of the raw
  PersistenceVersionConflictError; the service translates the
  persistence-layer conflict before it reaches the controller, so
  the previous catch was dead code. Drop the now-unused import.
- execution_lifecycle.handle_task_state_changed: extract the
  post-conflict retry path into _retry_after_version_conflict so
  the function stays under PLR0911. The retry now also short-
  circuits when the refreshed execution's node already matches the
  terminal status implied by the event, preventing a duplicate
  COMPLETED -> COMPLETED hop that would bump version + emit a
  bogus same-status transition. New helper
  _expected_terminal_node_status maps event -> node status; reuses
  the existing _node_status_for_task lookup.

Test alignment:
- test_execution_lifecycle.py FakeExecutionRepo.save: missing-row
  with version != 1 now raises RecordNotFoundError to mirror the
  real SQLite + Postgres repos so the new delete-race branch in
  handle_task_state_changed is exercised here.
- test_company.py taskgroup-error test: AsyncMock on
  resolver.get_str now declares spec=original_get_str.

Docs:
- XXEDetectedError docstring now correctly describes the explicit
  except XXEDetectedError catch in HTMLParseGuard.sanitize ahead of
  the generic except Exception branch (the prior wording said the
  generic branch caught it).
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/synthorg/engine/workflow/execution_lifecycle.py`:
- Around line 351-359: The retry block currently swallows all exceptions in the
except Exception: clause; change it to catch only the specific
persistence/version conflict exceptions that handle_task_state_changed expects
(the same two exception types used in the outer handler) — e.g., replace except
Exception: with except <VersionConflictException>, <OptimisticLockException>: —
log the retry failure with logger.exception(retry_event,
execution_id=execution.id, task_id=event.task_id, error="Retry after version
conflict also failed") and re-raise any other exceptions (do not swallow them)
so fatal errors like MemoryError/RecursionError propagate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d8f6f15-399c-4f5c-967b-fc549380a0c6

📥 Commits

Reviewing files that changed from the base of the PR and between ec8cbb2 and 8714c5e.

📒 Files selected for processing (7)
  • src/synthorg/api/controllers/workflows.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/config/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/tools/html_parse_guard.py
  • tests/unit/api/controllers/test_company.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lighthouse Site
  • GitHub Check: Build Web Assets (melange)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use from __future__ import annotations in Python 3.14+ code since PEP 649 is natively supported.

PEP 758 exception syntax: use except A, B: (no parens) when not binding; as exc requires parens.

All public functions require type hints; mypy strict mode is mandatory.

Google style docstrings on public classes and functions (ruff D rules enforced).

Never mutate objects; create new objects via model_copy(update=...) or copy.deepcopy(). Frozen Pydantic for config/identity; MappingProxyType for non-Pydantic registries.

Separate frozen config models from mutable-via-copy runtime models; never mix in one model.

Use Pydantic v2 with ConfigDict(frozen=True, allow_inf_nan=False) everywhere; extra="forbid" on every model that doesn't round-trip through model_dump() (request DTOs always).

Use @computed_field for derived values in Pydantic models.

Use NotBlankStr from core.types for identifier and name fields.

Prefer asyncio.TaskGroup for fan-out / fan-in; wrap independent task bodies in async def helpers that catch Exception (re-raise only MemoryError / RecursionError).

Never call lxml.html.fromstring on attacker input; use HTMLParseGuard instead.

Line length 88 (ruff); functions <50 lines; files <800 lines.

Comments explain WHY only, never origin / review / issue context. Forbidden: reviewer citations, in-code issue back-refs, naked SEC-1 taxonomy in src/, migration framing, round narrative, self-evident restatements.

Persistence boundary per-line opt-out: # lint-allow: persistence-boundary -- <reason>.

Files:

  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/config/errors.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_company.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/controllers/workflows.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Args models must be defined at every system boundary (BaseTool, MCP tool, A2A RPC, WebSocket event) using typed Pydantic args model validated before dispatch.

Every dict ingestion from an external source must call parse_typed() from synthorg.api.boundary with a hardcoded LiteralString boundary label.

Classes that read time or sleep should take clock: Clock | None = None (default SystemClock()); tests inject FakeClock.

Async start() / stop() services own a dedicated self._lifecycle_lock; timed-out stops mark the service unrestartable.

Wrap attacker-controllable strings via wrap_untrusted() from synthorg.engine.prompt_safety; append untrusted_content_directive(tags).

Pluggable subsystems must follow: protocol + strategy + factory + config discriminator with safe defaults.

Handle errors explicitly, never swallow. Domain error families register a base-class entry in EXCEPTION_HANDLERS (src/synthorg/api/exception_handlers.py). Use <Domain><Condition>Error inheriting from DomainError.

Never use Exception / RuntimeError / LookupError / PermissionError / ValueError / TypeError / KeyError / IndexError / AttributeError / OSError / IOError as direct base classes in src/synthorg/; use DomainError or a domain-specific base.

Every business-logic module must import from synthorg.observability import get_logger then logger = get_logger(__name__). Variable name always logger.

Never import logging / logging.getLogger() / print() in application code (carve-out: observability/{setup,sinks,*_handler}.py for handler bootstrap).

Import event name constants from synthorg.observability.events.<domain>; never use string literals for event names.

Use structured logging kwargs: logger.info(EVENT, key=value); never logger.info("msg %s", val).

Error paths must log at WARNING or ERROR with context before raising / returning.

State transitions log INFO via *_STATUS_TRANSITIONED constants (with `from_st...

Files:

  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/config/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/controllers/workflows.py
{src/synthorg/**/*.py,tests/**/*.py}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code/tests/comments/docstrings/configs. Use example-provider, example-{large,medium,small}-001 or test-provider, test-small-001.

Files:

  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/config/errors.py
  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_company.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/controllers/workflows.py
{web/src/**/*.{ts,tsx},src/synthorg/**/*.py}

📄 CodeRabbit inference engine (CLAUDE.md)

Currency, locale, timezone, date/number formats flow through @/utils/format + @/utils/locale (frontend) and DEFAULT_CURRENCY from synthorg.budget.currency (backend); no _usd suffixes; metric units only.

Regional defaults enforced by scripts/check_web_design_system.py, scripts/check_backend_regional_defaults.py, and scripts/check_forbidden_literals.py. Per-line opt-out: # lint-allow: regional-defaults.

Files:

  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/config/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/controllers/workflows.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/tools/html_parse_guard.py
  • src/synthorg/config/errors.py
  • src/synthorg/engine/workflow/execution_lifecycle.py
  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/controllers/workflows.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never delete, skip, or xfail failing tests; never use --no-verify; never edit tests/baselines/unit_timing.json.

Mark tests with @pytest.mark.unit / integration / e2e / slow.

Every Mock() / AsyncMock() / MagicMock() in tests/ MUST declare spec=ConcreteClass. Pre-existing sites frozen in scripts/mock_spec_baseline.txt.

Use mock_dispatcher from tests/conftest.py (AsyncMock(spec=NotificationDispatcher)) for shared mocks.

Time-driven tests must import FakeClock from tests._shared.fake_clock and inject via clock= parameter.

Never use monkeypatch.setattr(module.logger, "info", spy) antipattern; the BoundLoggerLazyProxy caches stale bound methods. Use try/finally del proxy.<level> instead.

Prefer @pytest.mark.parametrize for similar test cases.

Use Hypothesis for property-based testing (Python). Failure is a real bug: fix it and add an @example(...) decorator.

For 'block until cancelled' scenarios, use asyncio.Event().wait() not asyncio.sleep(large).

Files:

  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_company.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/engine/workflow/test_execution_lifecycle.py
  • tests/unit/api/controllers/test_company.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Validate at system boundaries (user input, external APIs, config files).

WebSocket per-frame timeout (DoS): silent peer closed with code 1008 after api.ws_frame_timeout_seconds (default 30s). Revalidation failures tracked via _SlidingWindowRateLimiter.

Controllers and API endpoints access persistence through domain-scoped service layers (e.g. ArtifactService, WorkflowService, MemoryService); services centralize audit logging.

Files:

  • src/synthorg/api/exception_handlers.py
  • src/synthorg/api/controllers/workflows.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: ALWAYS read the relevant `docs/design/` page before implementing or planning. Deviations require explicit user approval; update the design page when an approved deviation lands.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding. Be critical at every phase; surface improvements as suggestions, not silent changes. Prioritize by dependency order, not priority labels.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Always run tests with `pytest-xdist -n 8 --dist=loadfile` (parallelism required).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: After failing tests, run `scripts/run_affected_tests.py` for isolation regression gate via `pytest-repeat --count 2 -x`. Opt out via `SYNTHORG_SKIP_ISOLATION_GATE=1`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Commit format: `<type>: <description>` (feat/fix/refactor/docs/test/chore/perf/ci); enforced by commitizen.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Signed commits required on every protected ref. GPG/SSH signed, OR GitHub App-signed via the `synthorg-repo-bot`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Use branch naming `<type>/<slug>` from main.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Pre-commit hooks must follow `.pre-commit-config.yaml` configuration including ruff, gitleaks, hadolint, no-em-dashes, no-redundant-timeout, check-single-migration-per-pr.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Hookify rules in `.claude/hookify.*.md`: `block-pr-create` (must use `/pre-pr-review`), `block-double-push`, `enforce-parallel-tests` (`-n 8`), `no-cd-prefix`, `no-local-coverage`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Pre-push hooks include mypy, pytest (affected modules), golangci-lint, go vet, go test, eslint-web, and conditional checks.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Use `gh issue list` for GitHub issue queries in Bash, NOT MCP `list_issues`.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Merge strategy: squash. PR body becomes the squash commit message; trailers (`Release-As`, `Closes `#N``) must be in the PR body.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Preserve existing `Closes `#NNN`` in PR issue references; never remove unless explicitly asked.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: After finishing an issue: create branch (`<type>/<slug>`), commit, push. Do NOT auto-create PR.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: ALWAYS use `/pre-pr-review` to create PRs (trivial/docs-only: `/pre-pr-review quick`).
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Fix EVERYTHING valid review agents find, including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: Any PR that establishes or expands a project-wide convention MUST include the AST/script gate that prevents regression.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: PRs proposing a convention without enforcement are rejected.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: The gate's job is to catch the SECOND occurrence of the category; the audit's job is finding the FIRST.
Learnt from: CR
Repo: Aureliolo/synthorg

Timestamp: 2026-05-04T21:51:25.851Z
Learning: No default may privilege a region, currency, or locale. Resolution: user/company → browser/system → neutral fallback.
🔇 Additional comments (5)
src/synthorg/tools/html_parse_guard.py (1)

73-81: Looks good — intentional stdlib-base exception is properly documented and safely handled.

The per-line domain-error-hierarchy opt-out is present, and the explicit except XXEDetectedError path in sanitize() preserves the intended safe-empty behavior without double-emitting parse errors.

src/synthorg/api/controllers/workflows.py (1)

469-481: Conflict mapping now matches the service contract.

Catching WorkflowDefinitionRevisionMismatchError here is the correct translation target and preserves the expected HTTP 409 path for optimistic-concurrency conflicts.

tests/unit/api/controllers/test_company.py (1)

108-114: LGTM!

The test correctly validates the domain error migration: SettingNotFoundError now maps to HTTP 404 via handle_domain_error, and the mock properly uses spec=original_get_str per project guidelines.

src/synthorg/config/errors.py (1)

28-42: LGTM!

The migration to DomainError is correctly implemented with proper ClassVar declarations matching the base class contract. The instance attributes (message, locations) are appropriately distinguished from ClassVars.

src/synthorg/api/exception_handlers.py (1)

31-31: A2AClientError correctly inherits from DomainError with all required ClassVars.

✓ Verified: A2AClientError properly inherits from DomainError and declares all required ClassVars (status_code, error_code, error_category, default_message), so handle_domain_error can extract metadata correctly. The registration in EXCEPTION_HANDLERS is valid.

Comment thread src/synthorg/engine/workflow/execution_lifecycle.py
CodeRabbit re-review found the retry path in
``_retry_after_version_conflict`` swallowed every ``Exception`` via
``logger.exception`` without re-raising, which would absorb
``MemoryError`` / ``RecursionError`` and any other unexpected
failure (silent-failure violation, against the explicit "never
swallow" rule).

Replace the bare ``except Exception:`` with the project's
canonical pattern:

- ``except MemoryError, RecursionError: raise`` -- propagate
  process-level failures.
- ``except RecordNotFoundError`` -- the row was deleted between
  refresh and retry; log and drop, matching the outer handler's
  RecordNotFoundError branch.
- ``except PersistenceVersionConflictError`` -- another concurrent
  writer raced us mid-retry; log the second conflict and drop. The
  outer handler already chose to give up after one retry to avoid
  an unbounded loop.

Anything else now propagates so unknown failures surface instead
of being absorbed.
@Aureliolo Aureliolo merged commit 78a4439 into main May 4, 2026
77 checks passed
@Aureliolo Aureliolo deleted the chore/audit-followup-c-1738 branch May 4, 2026 22:37
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview May 4, 2026 22:37 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audit 2026-05-03 follow-up C: Error-handling convention rollout (~80-120 classes -> DomainError) + AST gate

1 participant